﻿using Algorithm.Check;
using Common;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

using static Algorithm.Check.ExtensionForCRC16;

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.

namespace NewDevice.RS485
{
    /// <summary>
    /// PQW_IO继电器模块控制类 提供基于Modbus协议的继电器控制功能
    /// </summary>
    public class PQW_IO
    {
        private readonly string _deviceName;
        private readonly OldSerialBase _serial;
        private readonly object _locker = new object();

        // Modbus 功能码
        private const byte ReadDoStatusFunctionCode = 0x01;  // 读取输出状态功能码

        private const byte ReadDiStatusFunctionCode = 0x02;  // 读取输入状态功能码
        private const byte WriteSingleCoilFunctionCode = 0x05;  // 写单个线圈功能码
        private const byte WriteSingleRegisterFunctionCode = 0x06;  // 写单个寄存器功能码
        private const byte WriteMultipleCoiFunctionCode = 0x0F;  // 写多个线圈功能码

        // 寄存器地址
        private const int OffAllRegisterAddress = 0x85;  // 关闭所有继电器的寄存器地址

        /// <summary>
        /// 数字输入状态字典
        /// Key: 从机ID, Value: 输入状态字节数组
        /// </summary>
        public ConcurrentDictionary<int, byte[]> DiStatus { get; set; } = new ConcurrentDictionary<int, byte[]>();

        /// <summary>
        /// 数字输出状态字典
        /// Key: 从机ID, Value: 输出状态字节数组
        /// </summary>
        public ConcurrentDictionary<int, byte[]> DoStatus { get; set; } = new ConcurrentDictionary<int, byte[]>();

        /// <summary>
        /// 数字输入通道数量
        /// </summary>
        public int DiCount { get; set; } = 4;

        /// <summary>
        /// 数字输出通道数量
        /// </summary>
        public int DoCount { get; set; } = 4;

        private readonly int _slaveId;

        /// <summary>
        /// di update event
        /// </summary>
        public Action<byte[]> DiUpdateEvent;

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="bus"></param>
        /// <param name="slaverId"></param>
        /// <param name="devName"></param>
        public PQW_IO(OldSerialBase bus, int slaverId, string devName = "PQW_IO继电器模块")
        {
            _serial = bus;
            this._slaveId = slaverId;
            this._deviceName = devName;
        }

        /// <summary>
        /// 获取指定从机的所有数字输入状态
        /// </summary>
        public void GetDi()
        {
            int channelIndex = 0;
            byte[] requestData =
            [
                (byte)_slaveId,
                ReadDiStatusFunctionCode,
                (byte)((channelIndex & 0xff00) >> 8),
                (byte)(channelIndex & 0xff),
                (byte)((DiCount & 0xff00) >> 8),
                (byte)(DiCount & 0xff)
            ];

            byte[] response = SendCommand(requestData, 5 + ((DiCount + 8 - 1) / 8));
            var diStaus = response.Skip(3).Take(response[2]).ToArray();
            DiStatus.AddOrUpdate(_slaveId, diStaus, (key, oldValue) => diStaus);
            DiUpdateEvent?.Invoke(diStaus);
        }

        /// <summary>
        /// 获取指定从机和通道的数字输入状态
        /// </summary>
        /// <param name="channelIndex">通道索引</param>
        /// <returns>通道状态</returns>
        /// <exception cref="Exception">当从机ID不存在时抛出异常</exception>
        public bool GetDi(int channelIndex)
        {
            if (DiStatus.TryGetValue(_slaveId, out var value))
            {
                var sts = (value[channelIndex / 8] & (1 << channelIndex % 8)) > 0;
                return sts;
            }
            throw new Exception($"{_deviceName} slaveId[{_slaveId}] not contained");
        }

        /// <summary>
        /// 获取指定从机和通道的数字输出状态
        /// </summary>
        /// <param name="channelIndex">通道索引</param>
        /// <returns>通道状态</returns>
        /// <exception cref="Exception">当从机ID不存在时抛出异常</exception>
        public bool GetDo(int channelIndex)
        {
            if (DoStatus.TryGetValue(_slaveId, out var value))
            {
                var sts = (value[channelIndex / 8] & (1 << channelIndex % 8)) > 0;
                return sts;
            }
            throw new Exception($"{_deviceName} slaveId[{_slaveId}] DO not contained");
        }

        /// <summary>
        /// 获取指定从机的所有数字输出状态
        /// </summary>
        public void GetDo()
        {
            int channelIndex = 0;
            byte[] requestData =
            [
                (byte)_slaveId,
                ReadDoStatusFunctionCode,
                (byte)((channelIndex & 0xff00) >> 8),
                (byte)(channelIndex & 0xff),
                (byte)((DoCount & 0xff00) >> 8),
                (byte)(DoCount & 0xff)
            ];

            byte[] response = SendCommand(requestData, 5 + ((DoCount + 8 - 1) / 8));
            var doStaus = response.Skip(3).Take(response[2]).ToArray();
            DoStatus.AddOrUpdate(_slaveId, doStaus, (key, oldValue) => doStaus);
        }

        /// <summary>
        /// 设置数字输出通道状态
        /// </summary>
        /// <param name="channel">通道索引</param>
        /// <param name="status">要设置的状态</param>
        public void SetDo(int channel, bool status)
        {
            int channelIndex = channel;
            byte[] requestData =
            [
                (byte)_slaveId,
                WriteSingleCoilFunctionCode,
                (byte)((channelIndex & 0xff00) >> 8),
                (byte)(channelIndex & 0xff),
                (byte)(status ? 0xff : 0x00),
                0x00
            ];

            SendCommand(requestData, requestData.Length + 2);
        }

        /// <summary>
        /// 关闭指定从机的所有继电器
        /// </summary>
        public void OffAll()
        {
            byte[] requestData =
            [
                (byte)_slaveId,
                WriteSingleRegisterFunctionCode,
                0x00,
                OffAllRegisterAddress,
                0x00,
                0x00
            ];

            SendCommand(requestData, requestData.Length + 2);
        }

        /// <summary>
        /// 发送Modbus命令并接收响应
        /// </summary>
        /// <param name="requestData">请求数据</param>
        /// <param name="expectedResponseLength">期望的响应长度</param>
        /// <returns>响应数据</returns>
        /// <exception cref="Exception">当响应长度不符合预期时抛出异常</exception>
        private byte[] SendCommand(byte[] requestData, int expectedResponseLength)
        {
            byte[] command = requestData.WithCRC16(CRC16Type.Modbus).ToArray();
            _serial.deviceName = _deviceName;
            byte[] resBytes = new byte[expectedResponseLength];
            for (int i = 0; i < 5; i++)
            {
                byte[] response = _serial.SendBytesAndWaitResponse(command, ReceivedDataCheck.FuncModbusCRC16Check, 1000);

                if (response.Length == expectedResponseLength)
                {
                    Array.Copy(response, resBytes, response.Length);
                    break;
                }
                else
                {
                    if (i == 4)
                    {
                        throw new Exception($"[{_deviceName}] 返回数据长度异常，期望长度: {expectedResponseLength}, 实际长度: {response.Length}");
                    }
                }
            }
            return resBytes;
        }
    }
}